
        PAGE    60,132
        TITLE   'Step INT 13H Calls'
        NAME    STEP13
;
;       ***********************************************************
;       *                                                         *
;       *                    S  T  E  P  1  3                     *
;       *                                                         *
;       *                        Rev 1.0                          *
;       *                                                         *
;       *                      Nov 20, 1987                       *
;       *                                                         *
;       *                      Mike Parker                        *
;       *                 2374 Meadowpark Court                   *
;       *               Maryland Heights, MO 63043                *
;       *                                                         *
;       *                     CIS 70270,161                       *
;       *                                                         *
;       * STEP13 intercepts BIOS INT 13h calls and displays a     *
;       * window that shows the function being called and the     *
;       * parameters being passed to it. Program execution will   *
;       * resume when a key is pressed. This allows you to single *
;       * step through accesses to the disk at the BIOS level.    *
;       *                                                         *
;       * STEP13 can be activated and de-activated through key-   *
;       * board commands.                                         *
;       *                                                         *
;       ***********************************************************
;
;
;
TESTING    EQU    0

BG         EQU    10H               ; BG sets background color (10h = BLUE)

BLACK      EQU    00H + BG
BLUE       EQU    01H + BG
GREEN      EQU    02H + BG
CYAN       EQU    03H + BG
RED        EQU    04H + BG
MAGENTA    EQU    05H + BG
BROWN      EQU    06H + BG
WHITE      EQU    07H + BG
GRAY       EQU    08H + BG
LTBLUE     EQU    09H + BG
LTGREEN    EQU    0AH + BG
LTCYAN     EQU    0BH + BG
LTRED      EQU    0CH + BG
LTMAGENTA  EQU    0DH + BG
YELLOW     EQU    0EH + BG
HIWHITE    EQU    0FH + BG
;
CR         EQU    13
LF         EQU    10
BIGR       EQU    1352H             ; 'R' key
SMALLR     EQU    1372H             ; 'r'
BIGS       EQU    1F53H             ; 'S' key
SMALLS     EQU    1F73H             ; 's'
ESCAPE     EQU    011BH
HOME       EQU    4700H
UPARROW    EQU    4800H
PGUP       EQU    4900H
LEFTARROW  EQU    4B00H
RIGHTARROW EQU    4D00H
XEND       EQU    4F00H
DOWNARROW  EQU    5000H
PGDN       EQU    5100H
;
BLANK      EQU    32
BRDROW     EQU    205
BRDCOL     EQU    186
ULC        EQU    201
URC        EQU    187
LLC        EQU    200
LRC        EQU    188
;
REGBX      EQU    00                ; offsets on saved register stack
REGCX      EQU    02
REGDX      EQU    04
REGDI      EQU    06
REGSI      EQU    08
REGBP      EQU    10
REGES      EQU    12
;
; Format of Step_Flg
;
STOP      EQU    01                  ; stop and display if bit 0 = 1
TERM      EQU    02                  ; terminate STEP13 if bit 1 = 1
;
IF TESTING
INT13OFF   EQU    0e0h*4            ; during testing an unused INT is used
ELSE
INT13OFF   EQU    13h*4
ENDIF

INT1cOFF   EQU    1ch*4
INT28OFF   EQU    28h*4

Code    SEGMENT PUBLIC PARA 'CODE'

        ASSUME  CS:Code, DS:Code

        ORG     100h

Main:                               ; starting point
        jmp     SHORT Main_1

Res_Str db      'STEP13'            ; Resident ID string to determine if
        db      0                   ;  already resident
;
Main_1:
        cli
        cld
        mov     AX,CS
        mov     SS,AX
        mov     SP,OFFSET Pstack
        mov     BX,DS               ; get difference between segments
        sub     AX,BX               ;  so .EXE file can run during testing
        sti
;
        mov     BX,OFFSET PRSIZE    ; program size in bytes
        mov     CL,4
        shr     BX,CL               ; convert it to paragraphs
        add     BX,AX               ; add difference between segments
        inc     BX                  ; make it next multiple of 16 bytes
        mov     CX,BX               ; CX = number paragraphs in this program
                                    ;  offset from PSP base
;
        mov     BX,002ch
        mov     BX,[BX]             ; get segment addr of environment from PSP
;
        mov     AX,CS
        mov     DS,AX               ; change DS and ES to local area
        mov     SegPSP,ES           ; save starting segment to release later
        mov     ES,AX
        mov     SegEnv,BX           ; save environment segment to release also
        mov     SegEND,CX           ; now save CX
;
; Now that segment registers are all in order see if this program is already
; resident by finding out who currently has INT 13h.
; Get segment of the program and look for ID string found at start of this
; program.
;
        push    ES
        mov     AL,13h
        mov     AH,35h
        int     21h                 ; return address in ES:BX
        call    Cmp_Str             ; search for ID string
        pop     ES
        jnz     chkeq               ; not found, continue
;
; This program already installed
;
        mov     DX,OFFSET ERRmsg
        mov     AH,9
        int     21h
;
        mov     AH,4ch              ; terminate (without staying resident)
        mov     AL,00
        int     21h

;
; Check equipment list to see if we have graphics or monochrome card
;
chkeq:
        int     11h
        and     AX,0030h            ; save video mode
        mov     BX,0b000h
        cmp     AX,0030h            ; monochrome?
        jz      setcrd
        mov     BX,0b800h           ; no, graphics card
setcrd:
        mov     Scrseg,BX           ; save base segment
;
; Calculate starting address on screen for window
;
        mov     BX,OFFSET W1        ; window parameters
        call    Window_Parms        ; calc start addr of window, wid and ht
;
; Make room for window save area - AX is number of bytes needed for window
;
        mov     CL,4
        shr     AX,CL               ; divide by 16 to get # paragraphs
        inc     AX                  ; handle any remainder
        mov     BX,SegPSP           ; starting PSP segment
        add     BX,SegEND           ; size of program in paragraphs
        mov     Winseg,BX           ; starting segment of window save area
        add     SegEND,AX           ; expand total space needed by this
                                    ;  programs code and window save area
;
; Redirect INT 13h, INT 28h and INT 1ch
;
        push    ES
        cli
        mov     AX,0
        mov     ES,AX
        mov     AX,OFFSET New13
        xchg    AX,ES:[INT13OFF]
        mov     Save13,AX
        mov     AX,CS
        xchg    AX,ES:[INT13OFF+2]
        mov     Save13+2,AX

IFE TESTING
        mov     AX,OFFSET New1c
        xchg    AX,ES:[INT1cOFF]
        mov     Save1c,AX
        mov     AX,CS
        xchg    AX,ES:[INT1cOFF+2]
        mov     Save1c+2,AX
;
        mov     AX,OFFSET New28
        xchg    AX,ES:[INT28OFF]
        mov     Save28,AX
        mov     AX,CS
        xchg    AX,ES:[INT28OFF+2]
        mov     Save28+2,AX
ENDIF

        sti
        pop     ES
;
        mov     DX,OFFSET INITmsg
        mov     AH,9
        int     21h

IF TESTING
        mov     SP,OFFSET Tstack
        or      BYTE PTR CS:Step_Flg,STOP    ; set flag to stop and display

        include test13.asm
;
        mov     AH,4ch              ; terminate
        mov     AL,00
        int     21h
ELSE
;
; Terminate and stay resident
;
        mov     AL,00
        mov     AH,31h
        mov     DX,SegEND           ; segment offset to release free memory
        int     21h                 ; won't return
ENDIF

;
; Compare string in program to ID string
; Entry:
;    ES = segment of owning process
;    DS = current programs segment
;
; Return:
;    ZF = 1 if strings equal (TSR already installed)
;    ZF = 0 if not found
;
Cmp_Str:
        mov     SI,OFFSET Res_Str
        mov     DI,SI               ; source and destination offsets the same
        mov     BX,-1               ; assume not found here
cmp1:
        lodsb
        scasb
        jnz     neql
        or      AL,AL               ; terminating null
        jnz     cmp1
        mov     BX,0                ; found string
neql:
        mov     AX,BX
        or      AX,AX
        ret

;
; Restore vectors and free resident program area.
; Interrupts are off upon entry.
;
Restore:
        push    AX
        push    DS
        push    ES
;
        mov     AX,CS
        mov     DS,AX               ; load my data segment
;
        mov     AX,0
        mov     ES,AX
        mov     AX,Save13           ; restore INT vectors
        mov     ES:[INT13OFF],AX
        mov     AX,Save13+2
        mov     ES:[INT13OFF+2],AX

IFE TESTING
        mov     AX,Save28
        mov     ES:[INT28OFF],AX
        mov     AX,Save28+2
        mov     ES:[INT28OFF+2],AX
;
        mov     AX,Save1c
        mov     ES:[INT1cOFF],AX
        mov     AX,Save1c+2
        mov     ES:[INT1cOFF+2],AX
ENDIF

;
; Free allocated memory from environment and this program
;
        mov     ES,SegEnv
        mov     AH,49h
        int     21h
;
        mov     ES,SegPSP           ; start of this programs segment
        mov     AH,49h
        int     21h
;
        pop     ES
        pop     DS
        pop     AX
        ret

;
; INT 1ch will be redirected to here each time a timer tick occurs.
; Do keyboard BIOS call to get state of control and shift keys.
; If both LEFT and RIGHT shift keys down, enable the Int 13 intercept code.
; If CONTROL key and LEFT and RIGHT shift keys down, set the terminate flag.
; On the next Int 28h call by DOS, we will remove this program from memory.
;
New1c:
       push     AX
       mov      AH,02               ; get shift status
       int      16h
       and      AL,07               ; save control, left and right shift status
       cmp      AL,03               ; are they both down?
       jnz      tstctl              ; jump if no
       or       BYTE PTR CS:Step_Flg,STOP    ; set flag to stop and display Int 13h
tstctl:
       cmp      AL,07               ; three keys down?
       jnz      n1cout
       or       BYTE PTR CS:Step_Flg,TERM    ; set terminate flag
n1cout:
       pop      AX
       jmp      CS:[DWORD PTR Save1c]        ; continue in the chain

;
; The DOS idle loop will be redirected to here.
; Check for TERMINATE bit set. If so, free all memory used by this TSR program
; and restore all vectors to their original values.
;
New28:
       test     BYTE PTR CS:Step_Flg,TERM
       jz       n28out
       call     Restore
       cli
n28out:
       jmp      CS:[DWORD PTR Save28]        ; continue in the chain

;
; INT 13h will be redirected to here
;
New13:
        push    AX                  ; use USERS'S stack for first 3 words
        push    DS
        pushf                       ; push them but we don't need them here
;
        cli
        mov     AX,CS               ; now switch to internal stack
        mov     DS,AX
        mov     SSsave,SS
        mov     SPsave,SP
        mov     SS,AX
        mov     SP,OFFSET Pstack
        sti
;
        push    ES
        push    BP
        push    SI
        push    DI
        push    DX
        push    CX
        push    BX
        mov     BP,SP               ; save base pointer to display reg's
        mov     ES,AX
        test    BYTE PTR Step_Flg,STOP    ; should we stop and display?
        jz      norun               ; no, restore registers and leave
;
; Recover registers on USER'S stack
;
        push    ES
        les     BX,Ssave
        mov     AX,ES:[BX+2]        ; get DS
        mov     DSsave,AX
        mov     AX,ES:[BX+4]        ; get AX
        mov     AXsave,AX
        mov     Last_FC,AH          ; save as last function code
        mov     AX,ES:[BX+6]        ; get IP
        mov     IPsave,AX
        mov     AX,ES:[BX+8]        ; get CS
        mov     CSsave,AX
        mov     AX,ES:[BX+10]       ; get FLAGS on entry
        mov     FLsave,AX
        pop     ES
;
        cld                         ; clear direction flag for string operations
        test    BYTE PTR Skip_Flg,0ffh    ; skip certain functions?
        jz      noskip              ; no, continue
        mov     AX,AXsave           ; get USER's AX register with function code
        cmp     AH,Skip_Typ         ; compare upper half to type to skip
        jz      norun               ; don't want to break on this one again
        mov     BYTE PTR Skip_Flg,00      ; skip flag was set but this is new
                                    ;  function, so break from now on
noskip:
        call    Open_Window
        call    Show_Func           ; show function number
        call    Disp_Text           ; display description of BIOS function
        call    Show_Help
;
        call    Get_Key             ; wait for a key pressed
        call    Disp_Key            ; dispatch to key handler
        jc      noskip              ; if carry set, need to redisplay window
        call    Close_Window
norun:
        pop     BX
        pop     CX
        pop     DX
        pop     DI
        pop     SI
        pop     BP
        pop     ES
        cli
        mov     SS,SSsave           ; switch back to user's stack
        mov     SP,SPsave
;
        mov     AX,FLsave           ; get original user's flags before INT 13h
        push    AX                  ; sneak them into flag register
        popf
;
        pop     AX                  ; dummy pop to get flags off stack
        pop     DS                  ; restore registers left on user's stack
        pop     AX                  ; now get real AX

IFE TESTING
        pushf
        cli                         ; give interrupt code IF = 0 like normal
        call    CS:[DWORD PTR Save13]
ENDIF

        push    AX                  ; use USERS'S stack for first 3 words
        push    DS
        pushf                       ; they will get used this time
;
        cli
        mov     AX,CS               ; now switch to internal stack
        mov     DS,AX
        mov     SSsave,SS
        mov     SPsave,SP
        mov     SS,AX
        mov     SP,OFFSET Pstack
        sti
;
        push    ES
        push    BP
        push    SI
        push    DI
        push    DX
        push    CX
        push    BX
        mov     BP,SP               ; save base pointer to display reg's
        mov     ES,AX
        cld                         ; clear direction flag for string operations
;
; Recover registers on USER'S stack
;
        push    ES
        les     BX,Ssave
        mov     AX,ES:[BX+0]        ; get FLAGS on exit
        mov     FLsave,AX
        mov     AX,ES:[BX+2]        ; get DS
        mov     DSsave,AX
        mov     AX,ES:[BX+4]        ; get AX
        mov     AXsave,AX
        mov     AX,ES:[BX+6]        ; get IP
        mov     IPsave,AX
        mov     AX,ES:[BX+8]        ; get CS
        mov     CSsave,AX
        pop     ES
;
        xor     AL,AL
        xchg    AL,Ret_Flg          ; read value and clear flag for next time
        test    AL,0ffh             ; is flag set to display return value?
        jz      nostop
;
        call    Open_Window
        call    Show_Func           ; show function number
        call    Disp_Ret            ; display return code in AX and flags
        call    Get_Key             ; wait for a key pressed
        call    Close_Window
nostop:
        pop     BX
        pop     CX
        pop     DX
        pop     DI
        pop     SI
        pop     BP
        pop     ES
        cli
        mov     SS,SSsave
        mov     SP,SPsave
        popf
        pop     DS
        pop     AX
        retf    2

;
; Display return code in AX, carry and zero flags
;
Disp_Ret:
        mov     CX,0301h
        mov     DX,OFFSET RCStr
        mov     DI,14
        mov     AX,AXsave
        call    OutWord
;
        mov     DX,OFFSET CFStr
        mov     DI,14
        add     DI,DX
        mov     AL,'0'
        test    FLsave,0001         ; carry flag is least significant bit
        jz      dcy
        inc     AL                  ; carry was set so change to '1'
dcy:
        mov     [DI],AL             ; store it in string
        add     CX,0100h            ; move cursor position
        call    Disp_Str
;
        mov     DX,OFFSET ZFStr
        mov     DI,14
        add     DI,DX
        mov     AL,'0'
        test    FLsave,0040h        ; test zero flag
        jz      dzf
        inc     AL                  ; zero was set so change to '1'
dzf:
        mov     [DI],AL             ; store it in string
        add     CX,0100h            ; move cursor position
        call    Disp_Str
;
        mov     CX,0a0ah            ; display some help
        mov     DX,OFFSET HlpStr5
        mov     AH,YELLOW
        call    WPrint
;
        mov     CX,0b0bh            ; move cursor position
        mov     DX,OFFSET HlpStr6
        mov     AH,YELLOW
        call    WPrint
        ret

;
; Display function code description in window
;
Show_Func:
        mov     CX,0007h            ; cursor position - row, col
        mov     DX,OFFSET FCStr     ; message address
        mov     DI,DX
        add     DI,19               ; offset this many into string
        mov     AL,Last_FC          ; get last function code
        call    Shexbyt             ; convert it to ASCII
        mov     BX,OFFSET W1        ; window
        mov     AH,YELLOW           ; attribute
        call    WPrint
        ret

;
; Display input parameters
;
Disp_Parms:
        call    Disp_Drv            ; show drive number
        call    Disp_Head           ; head number
        call    Disp_Trk            ; track number
        call    Disp_Sec            ; sector number
        ret

;
; Display input parameters for Hard Disk calls
; Displays "CYLINDER" instead of "TRACK"
;
Disp_HD_Parms:
        call    Disp_Drv
        call    Disp_Head
        call    Disp_Cyl            ; show cylinder number
        ret

;
; Display Drive ID
;
Disp_Drv:
        mov     CX,0401h
        mov     DX,OFFSET DrvStr
        mov     DI,DX
        add     DI,8
        mov     AX,[BP+REGDX]       ; DL register has drive #
        call    Shexbyt
        mov     AH,HIWHITE
        call    WPrint
        ret

;
; Display Head #
;
Disp_Head:
        add     CX,0100h
        mov     DX,OFFSET HedStr
        mov     DI,DX
        add     DI,8
        mov     AX,[BP+REGDX]       ; DH register has head #
        mov     AL,AH
        call    Sdecbyt
        mov     AH,HIWHITE
        call    WPrint
        ret

;
; Display Track #
;
Disp_Trk:
        add     CX,0100h
        mov     DX,OFFSET TrkStr
        mov     DI,DX
        add     DI,7
        mov     AX,[BP+REGCX]       ; CH register has track #
        mov     AL,AH
        xor     AH,AH               ; clear upper half
        call    HextoDec
        dec     SI                  ; back up to get three chars instead
        movsb                       ; of normal two
        movsb
        movsb
        mov     AH,HIWHITE
        call    WPrint
        ret

;
; Display Cylinder #
;
Disp_Cyl:
        add     CX,0100h
        mov     DX,OFFSET CylStr
        mov     DI,DX
        add     DI,7
        mov     AX,[BP+REGCX]       ; CH register has cylinder #
        mov     AL,AH
        xor     AH,AH               ; clear upper half
        call    HextoDec
        dec     SI
        movsb
        movsb
        movsb
        mov     AH,HIWHITE
        call    WPrint
        ret

;
; Display Sector #
;
Disp_Sec:
        add     CX,0100h
        mov     DX,OFFSET SecStr
        mov     DI,DX
        add     DI,8
        mov     AX,[BP+REGCX]       ; CL register has sector #
        call    Sdecbyt
        mov     AH,HIWHITE
        call    WPrint
        ret

;
; Display HELP in Window
;
Show_Help:
        mov     CX,0a03h            ; cursor position - row, col
        mov     AH,LTRED            ; attribute for char
        mov     AL,'S'
        call    WChar               ; write one character with attribute
;
        add     CX,0001h            ; add 1 to column number
        mov     DX,OFFSET HlpStr1   ; message address
        mov     AH,WHITE
        call    WPrint
;
        add     CX,0012h            ; move column number
        mov     AH,LTRED            ; attribute for char
        mov     AL,'R'
        call    WChar               ; write one character with attribute
;
        add     CX,0001h            ; move column number
        mov     DX,OFFSET HlpStr2
        mov     AH,WHITE
        call    WPrint
;
        mov     CX,0b01h            ; cursor position - row, col
        mov     AH,LTRED
        mov     AL,'E'
        call    WChar
;
        add     CX,0001h
        mov     AH,LTRED
        mov     AL,'S'
        call    WChar
;
        add     CX,0001h
        mov     AH,LTRED
        mov     AL,'C'
        call    WChar
;
        add     CX,0001h
        mov     DX,OFFSET HlpStr3
        mov     AH,WHITE
        call    WPrint
;
        add     CX,000dh
        mov     AH,LTRED
        mov     AL,18h              ; up arrow
        call    WChar
;
        add     CX,0001h
        mov     AH,LTRED
        mov     AL,19h              ; down arrow
        call    WChar
;
        add     CX,0001h
        mov     AH,LTRED
        mov     AL,1bh              ; left arrow
        call    WChar
;
        add     CX,0001h
        mov     AH,LTRED
        mov     AL,1ah              ; right arrow
        call    WChar
;
        add     CX,0001h
        mov     DX,OFFSET HlpStr4
        mov     AH,WHITE
        call    WPrint
        ret

;
; HextoDec converts a word in AX to an ASCII string
; Entry:
;      AX = word to convert
; Exit:
;      SI = Pointer to last two character of ASCII string in 'Astr'
;
HextoDec:
         push   AX
         push   CX
         push   DX
         push   DI
;
         mov    DI,OFFSET Astr
         mov    CX,10000
         xor    DX,DX
         div    CX                  ; num / 10000
         add    AL,'0'
         stosb
         mov    AX,DX
         mov    CX,1000
         xor    DX,DX
         div    CX                  ; num / 1000
         add    AL,'0'
         stosb
         mov    AX,DX
         mov    CX,100
         xor    DX,DX
         div    CX                  ; num / 100
         add    AL,'0'
         stosb
         mov    AX,DX
         mov    CX,10
         xor    DX,DX
         div    CX                  ; num / 10
         add    AL,'0'
         stosb
         mov    AX,DX
         add    AL,'0'
         stosb
         xor    AL,AL               ; <NULL> terminator in string
         stosb
         sub    DI,3                ; back up pointer
         mov    SI,DI
;
         pop    DI
         pop    DX
         pop    CX
         pop    AX
         ret

;
; Display the text description of the current BIOS function code
;
Disp_Text:
        mov     CX,LENFC / 6        ; number of entries
        mov     AX,AXsave           ; get user's function code
        cmp     AH,CL               ; is it in range?
        jnb     not_fnd             ; jump if no
        mov     DI,OFFSET FCtbl
cmpdt:
        cmp     AH,[DI+1]           ; is this the right function?
        jz      dtexec
        add     DI,6                ; no, point to next
        loop    cmpdt
not_fnd:
        mov     DX,OFFSET STRUN
        mov     CX,0201h
        call    Disp_Str
        ret

dtexec:
        mov     CX,0201h            ; row, col to position string
        mov     DX,[DI+2]           ; get pointer to string
        call    [WORD PTR DI+4]     ; go to display routine
        ret

;
; Display string of DOS function code description
;
; Entry:
;       DX = pointer to string
;       CX = Row, Col
;
Disp_Str:
        mov     BX,OFFSET W1
        mov     AH,HIWHITE
        call    WPrint
        ret

;
; Routines to build string for display - some values need to be filled in
; Entry:
;       DX = pointer to string
;       CX = Row, Col in window
;
; Read sectors
;
Disp_02:
        mov     DI,5
        add     DI,DX
        mov     AX,AXsave           ; AL has number of sectors to read
        call    Sdecbyt             ; convert it to ASCII decimal
        call    Disp_Str
        call    Disp_Parms          ; show input parameters
        call    Disp_Buf            ; show buffer addr in ES:BX
        ret
;
; Write sectors
;
Disp_03:
        mov     DI,6
        add     DI,DX
        mov     AX,AXsave           ; AL has number of sectors to write
        call    Sdecbyt             ; convert it to ASCII decimal
        call    Disp_Str
        call    Disp_Parms          ; show input parameters
        call    Disp_Buf
        ret

;
; Verify sectors
;
Disp_04:
        mov     DI,7
        add     DI,DX
        mov     AX,AXsave           ; AL has number of sectors to write
        call    Sdecbyt             ; convert it to ASCII decimal
        call    Disp_Str            ; show function description
        call    Disp_Parms          ; show input parameters
        ret

;
; Format track
;
Disp_05:
        call    Disp_Str
        call    Disp_Parms
        call    Disp_Buf
        ret

;
; Read and Write Long
;
Disp_0a:
Disp_0b:
        call    Disp_Str
        call    Disp_HD_Parms       ; show Drive, Head and Cylinder
        call    Disp_Sec            ; show Sector #
        call    Disp_Buf            ;  and Buffer address
        ret

;
; Seek to Cylinder
;
Disp_0c:
        call    Disp_Str
        call    Disp_HD_Parms       ; show Drive, Head and Cylinder
        ret

;
; Alternate Disk Reset
;
Disp_0d:
        call    Disp_Str
        call    Disp_Drv           ; show drive ID
        ret

;
; Test for Drive Ready
;
Disp_10:
        call    Disp_Str
        call    Disp_Drv
        ret

;
; Recalibrate Drive
;
Disp_11:
        call    Disp_Str
        call    Disp_Drv
        ret

;
; Get Disk Type
;
Disp_15:
        call    Disp_Str
        call    Disp_Drv
        ret

;
; Set Disk Type
;
Disp_17:
        mov     DI,17
        add     DI,DX
        mov     AX,AXsave           ; AL has disk type
        call    Shexbyt
        call    Disp_Str
;
        mov     AX,AXsave
        mov     DX,OFFSET Str170
        cmp     AL,00               ; no disk?
        jz      d17shw
        mov     DX,OFFSET Str171
        cmp     AL,01               ; regular diskette?
        jz      d17shw
        mov     DX,OFFSET Str173
        cmp     AL,03               ; high capacity diskette?
        jnz     d17drv
d17shw:
        add     CX,0100h
        call    Disp_Str
d17drv:
        call    Disp_Drv
        ret

;
; Display ES:BX registers
;
Disp_Buf:
        mov     DX,OFFSET BufStr    ; show buffer address
        mov     DI,17
        add     CX,0100h
        call    OutESBX
        ret

;
; Convert users's ES and BX to ASCII, imbed them in target string
; Entry:
;      DX = pointer to string
;      DI = offset where to put converted ES:BX
;      CX = Row, Col
;
OutESBX:
        add     DI,DX
        mov     AX,[BP+REGES]       ; get user's ES
        call    Shexwrd
        inc     DI                  ; skip past ':'
        mov     AX,[BP+REGBX]       ; get user's BX
        call    Shexwrd
        call    Disp_Str
        ret

;
; Convert word in AX to ASCII, imbed it in target string
; Entry:
;      AX = word to convert
;      CX = row, col
;      DX = pointer to string
;      DI = offset where to put converted AX
;
OutWord:
        add     DI,DX
        call    Shexwrd
        call    Disp_Str
        ret


Open_Window:
        push    BP
        push    ES
;
; Calculate current page offset each time since it can change at any time
;
        mov     AH,0fh              ; get current video state
        int     10h
        mov     CL,BH               ; returns BH = active page #
        xor     CH,CH
        mov     AX,0100h            ; page length in paragraphs
        mul     cx
        add     AX,Scrseg           ; add base segment of screen
        mov     Pageseg,AX
;
; Save current contents of window area
;
        mov     BX,OFFSET W1        ; window parameters
        mov     SI,[BX].startmem    ; screen addr of start of window
        mov     DX,[BX].height
        mov     ES,Winseg           ; point to allocated memory block
        mov     DI,0000
saverow:
        push    DS
        mov     CX,[BX].xwidth
        mov     DS,Pageseg          ; get screen segment
        push    SI                  ; save screen offset
rep     movsw                       ; do word to get char and attribute
        pop     SI
        pop     DS
        add     SI,BytesPL          ; next row down
        dec     DX
        jnz     saverow
;
; Draw window
;
        mov     ES,Pageseg          ; get screen segment
        mov     DI,[BX].startmem    ; physical addr of start of window
        mov     AH,HIWHITE
;
; Draw top border
;
        push    DI
        mov     AL,ULC              ; write upper left corner
        stosw
        mov     CX,[BX].xwidth
        sub     CX,2
        mov     AL,BRDROW
rep     stosw
        mov     AL,URC              ; write upper right corner
        stosw
        pop     DI
        add     DI,BytesPL          ; next row down
;
; Draw middle of window
;
        mov     DX,[BX].height
        sub     DX,2
winrows:
        push    DI
        mov     AL,BRDCOL           ; left border column
        stosw
        mov     CX,[BX].xwidth
        sub     CX,2
        mov     AL,BLANK
rep     stosw
        mov     AL,BRDCOL           ; right border column
        stosw
        pop     DI
        add     DI,BytesPL          ; next row down
        dec     DX
        jnz     winrows
;
; Draw bottom border
;
        push    DI
        mov     AL,LLC              ; write lower left corner
        stosw
        mov     CX,[BX].xwidth
        sub     CX,2
        mov     AL,BRDROW
rep     stosw
        mov     AL,LRC              ; write lower right corner
        stosw
        pop     DI
;
        pop     ES
        pop     BP
        ret


Close_Window:
        push    ES
        mov     BX,OFFSET W1
        mov     DI,[BX].startmem    ; physical addr of screen area
        mov     ES,Pageseg          ; point to allocated memory block
        mov     SI,0000             ; start of save memory block
        mov     DX,[BX].height
resrow:
        push    DS
        mov     CX,[BX].xwidth
        mov     DS,Winseg           ; get saved memory segment
        push    DI                  ; save screen offset
rep     movsw                       ; do word to get char and attribute
        pop     DI
        pop     DS
        add     DI,BytesPL          ; next row down
        dec     DX
        jnz     resrow
        pop     ES
        ret

;
; Print a string in window.
; Entry:
;        CX = Row, Col position relative to start of window
;        BX = Window pointer
;        AH = Attribute
;        DX = addr of string
;
WPrint:
        push    ES
        call    GetRC               ; convert to row, col in DI
        mov     ES,Pageseg
        mov     SI,DX               ; move string address
wpwrt:
        cmp     BYTE PTR [SI],00    ; test for string terminator <NULL>
        jz      wpexit
        lodsb
        stosw
        jmp     wpwrt
wpexit:
        pop     ES
        ret

;
; Write one character and attribute to window
;
; Entry:
;        CX = Row, Col position relative to start of window
;        BX = Window pointer
;        AH = attr
;        AL = char
;
WChar:
        push    ES
        call    GetRC               ; convert to row, col in DI
        mov     ES,Pageseg
        stosw
        pop     ES
        ret

;
; Convert row, col in CX to physical addr in DI
;
GetRC:
        push    AX
        push    DX
        mov     DI,[BX].startmem    ; physical start of window memory
        add     DI,BytesPL          ; go down a row
        add     DI,2                ; go in one char, this is 0,0
        mov     AL,CH
        xor     AH,AH               ; AX = relative row number
        mul     BytesPL             ; this clobbers DX
        add     DI,AX
        mov     AX,CX               ; restore coordinates
        xor     AH,AH               ; AX = relative col number
        shl     AX,1                ; times 2 for attribute
        add     DI,AX
        pop     DX
        pop     AX
        ret

;
; STORE HEX WORD
; Convert a hex word to ASCII and store it in destination string
; Entry:
;        AX = Word to convert
;        DI = destination pointer
;
Shexwrd:
        xchg    AL,AH               ; convert upper half first
        call    Shexbyt
        xchg    AL,AH               ; convert lower half
        call    Shexbyt
        ret

;
; STORE HEX BYTE
; Convert a hex byte to ASCII and store it in destination string
; Entry:
;        AL = Byte to convert
;        DI = destination pointer
;
Shexbyt:
        push    AX
        mov     AH,AL               ; save temporarily
        shr     AL,1
        shr     AL,1
        shr     AL,1
        shr     AL,1
        cmp     AL,10
        jb      wh1
        add     AL,07
wh1:
        add     AL,'0'
        stosb
        mov     AL,AH
        and     AL,0fh
        cmp     AL,10
        jb      wh2
        add     AL,07
wh2:
        add     AL,'0'
        stosb
        pop     AX
        ret

;
; STORE DECIMAL BYTE
; Convert a hex byte to ASCII decimal and store it in destination string
; Entry:
;        AL = Byte to convert
;        DI = destination pointer
;
Sdecbyt:
        and     AX,00ffh            ; mask off upper half
        call    HextoDec
        movsb                       ; copy 2 chars to destination string
        movsb
        ret

;
; Check if key pressed requires special handling
;
; Entry:
;      AX = key pressed
;
Disp_Key:
        mov     DI,OFFSET Keytbl    ; keys and subroutine addresses
        mov     CX,LENKTAB / 4      ; number of entries
cmpdk:
        cmp     AX,[DI]             ; is key in table?
        jz      dkexec
        add     DI,4                ; no, point to next key value
        loop    cmpdk
        clc                         ; carry clear means exit upon return
        ret
dkexec:
        add     DI,2
        call    [WORD PTR DI]
        ret

;
; Table of keys to watch for followed by the address of the routine to execute
;
Keytbl  dw      ESCAPE,     DO_Esc
        dw      BIGS,       Do_Skey
        dw      SMALLS,     Do_Skey
        dw      BIGR,       Do_Rkey
        dw      SMALLR,     Do_Rkey
        dw      UPARROW,    Do_Up
        dw      DOWNARROW,  Do_Down
        dw      LEFTARROW,  Do_Left
        dw      RIGHTARROW, Do_Right
        dw      HOME,       Do_Home
        dw      XEND,       Do_End
        dw      PGUP,       Do_Pgup
        dw      PGDN,       Do_Pgdn
LENKTAB EQU     $ - Keytbl

;
; If ESC key, clear flag so Int 13h will not be stopped each time
;
Do_Esc:
        and     BYTE PTR Step_Flg,255-STOP
        clc                         ; carry clear means exit upon return
        ret

;
; 'S' key - skip successive functions of current type.
; Return to intercepting calls when something new comes along.
;
Do_Skey:
        mov     AL,0ffh
        mov     Skip_Flg,AL         ; set flag to show we should skip something
        mov     AL,Last_FC          ; get current function code
        mov     Skip_Typ,AL         ; save it
        clc                         ; carry clear means exit upon return
        ret

;
; 'R' key - stop after INT 13h call and let user see results
;
Do_Rkey:
        mov     AL,0ffh
        mov     Ret_Flg,AL          ; set flag
        clc                         ; carry clear means exit upon return
        ret

;
; DO_Up - move window up one line
;
Do_Up:
        mov     BX,OFFSET W1
        call    Close_Window
        mov     AX,[BX].leftrow
        or      AX,AX               ; already at top of screen?
        jz      upexit
        dec     [BX].leftrow
        dec     [BX].rightrow
        call    Window_Parms        ; recalculate parameters
upexit:
        stc                         ; don't exit upon return
        ret

;
; DO_Down - move window down one line
;
Do_Down:
        mov     BX,OFFSET W1
        call    Close_Window
        mov     AX,[BX].rightrow
        cmp     AX,24               ; already at bottom of screen?
        jz      dnexit
        inc     [BX].leftrow
        inc     [BX].rightrow
        call    Window_Parms        ; recalculate parameters
dnexit:
        stc                         ; don't exit upon return
        ret

;
; DO_Left - move window left one column
;
Do_Left:
        mov     BX,OFFSET W1
        call    Close_Window
        mov     AX,[BX].leftcol
        or      AX,AX               ; already at left of screen?
        jz      lfexit
        dec     [BX].leftcol
        dec     [BX].rightcol
        call    Window_Parms        ; recalculate parameters
lfexit:
        stc                         ; don't exit upon return
        ret

;
; Do_Right - move window right one column
;
Do_Right:
        mov     BX,OFFSET W1
        call    Close_Window
        mov     AX,[BX].rightcol
        cmp     AX,79               ; already at right of screen?
        jz      rtexit
        inc     [BX].leftcol
        inc     [BX].rightcol
        call    Window_Parms        ; recalculate parameters
rtexit:
        stc                         ; don't exit upon return
        ret

;
; Do_Home - move window to upper left corner
;
Do_Home:
        mov     BX,OFFSET W1
        call    Close_Window
        xor     AX,AX
        mov     [BX].leftrow,AX
        mov     [BX].leftcol,AX
;
        mov     AX,[BX].leftrow
        add     AX,[BX].height
        dec     AX
        mov     [BX].rightrow,AX
;
        mov     AX,[BX].leftcol
        add     AX,[BX].xwidth
        dec     AX
        mov     [BX].rightcol,AX
;
        call    Window_Parms        ; recalculate parameters
        stc                         ; don't exit upon return
        ret

;
; Do_End - move window to lower left corner
;
Do_End:
        mov     BX,OFFSET W1
        call    Close_Window
        mov     AX,24
        mov     [BX].rightrow,AX
        xor     AX,AX
        mov     [BX].leftcol,AX
;
        mov     AX,[BX].rightrow
        sub     AX,[BX].height
        inc     AX
        mov     [BX].leftrow,AX
;
        mov     AX,[BX].leftcol
        add     AX,[BX].xwidth
        dec     AX
        mov     [BX].rightcol,AX
;
        call    Window_Parms        ; recalculate parameters
        stc                         ; don't exit upon return
        ret

;
; Do_Pgup - move window to upper right corner
;
Do_Pgup:
        mov     BX,OFFSET W1
        call    Close_Window
        xor     AX,AX
        mov     [BX].leftrow,AX
        mov     AX,79
        mov     [BX].rightcol,AX
;
        mov     AX,[BX].leftrow
        add     AX,[BX].height
        dec     AX
        mov     [BX].rightrow,AX
;
        mov     AX,[BX].rightcol
        sub     AX,[BX].xwidth
        inc     AX
        mov     [BX].leftcol,AX
;
        call    Window_Parms        ; recalculate parameters
        stc                         ; don't exit upon return
        ret

;
; Do_Pgdn - move window to lower right corner
;
Do_Pgdn:
        mov     BX,OFFSET W1
        call    Close_Window
        mov     AX,24
        mov     [BX].rightrow,AX
        mov     AX,79
        mov     [BX].rightcol,AX
;
        mov     AX,[BX].rightrow
        sub     AX,[BX].height
        inc     AX
        mov     [BX].leftrow,AX
;
        mov     AX,[BX].rightcol
        sub     AX,[BX].xwidth
        inc     AX
        mov     [BX].leftcol,AX
;
        call    Window_Parms        ; recalculate parameters
        stc                         ; don't exit upon return
        ret

;
; Wait for any key pressed
;
Get_Key:
        push    BP                  ; just in case
        mov     AH,00
        int     16h
        pop     BP
        ret

;
; Calculate window parameters:
;      1) Physical starting address of window
;      2) Width of window in characters
;      3) Height of window
;
; Entry:
;      BX = window pointer
; Exit:
;      AX = Number of bytes contained in window
;
Window_Parms:
        mov     AX,[BX].leftrow
        mul     BytesPL             ; multiply by bytes per line
        mov     CX,[BX].leftcol
        shl     CX,1                ; times 2 for attribute byte
        add     AX,CX
        mov     SI,AX               ; starting physical address of window
        mov     [BX].startmem,AX    ; save it for later
;
; Calculate size of window in bytes to allocate memory
;
        mov     AX,[BX].rightcol
        inc     AX
        sub     AX,[BX].leftcol
        mov     [BX].xwidth,AX
        mov     CX,[BX].rightrow
        inc     CX
        sub     CX,[BX].leftrow
        mov     [BX].height,CX
        mul     CL                  ; AX = nbr of bytes of screen area
        shl     AX,1                ; times 2 to get attributes also
        ret

;
; Table of function codes.
; (1st) word is DOS function code (AH),
; (2nd) word is pointer to string to display,
; (3rd) word is subroutine address to handle display of this type function.
;
FCtbl:
        dw      0000h, STR00, Disp_Str
        dw      0100h, STR01, Disp_Str
        dw      0200h, STR02, Disp_02
        dw      0300h, STR03, Disp_03
        dw      0400h, STR04, Disp_04
        dw      0500h, STR05, Disp_05
        dw      0600h, STRUN, Disp_Str
        dw      0700h, STRUN, Disp_Str
        dw      0800h, STR08, Disp_Str
        dw      0900h, STR09, Disp_Str
        dw      0a00h, STR0a, Disp_0a
        dw      0b00h, STR0b, Disp_0b
        dw      0c00h, STR0c, Disp_0c
        dw      0d00h, STR0d, Disp_0d
        dw      0e00h, STRUN, Disp_Str
        dw      0f00h, STRUN, Disp_Str
        dw      1000h, STR10, Disp_10
        dw      1100h, STR11, Disp_11
        dw      1200h, STRUN, Disp_Str
        dw      1300h, STRUN, Disp_Str
        dw      1400h, STR14, Disp_Str
        dw      1500h, STR15, Disp_15
        dw      1600h, STR16, Disp_Str
        dw      1700h, STR17, Disp_17
LENFC   EQU     $ - FCtbl
;
;
STR00   db      'Reset Disk System'
        db      0

STR01   db      'Get Disk Status'
        db      0

STR02   db      'Read XX Disk Sector(s)'
        db      0

STR03   db      'Write XX Disk Sector(s)'
        db      0

STR04   db      'Verify XX Disk Sector(s)'
        db      0

STR05   db      'Format Disk Track'
        db      0

STR08   db      'Get Current Drive Parameters'
        db      0

STR09   db      'Initialize Fixed-Disk Base Tables'
        db      0

STR0a   db      'Read Long'
        db      0

STR0b   db      'Write Long'
        db      0

STR0c   db      'Seek to Cylinder'
        db      0

STR0d   db      'Alternate Disk Reset'
        db      0

STR10   db      'Test For Drive Ready'
        db      0

STR11   db      'Recalibrate Drive'
        db      0

STR14   db      'Controller Diagnostics'
        db      0

STR15   db      'Get Disk Type'
        db      0

STR16   db      'Change of Disk Status'
        db      0

STR17   db      'Set Disk Type to XXH'
        db      0

STR170  db      'No Disk'
        db      0

STR171  db      'Regular Diskette'
        db      0

STR173  db      'High Capacity Diskette (1.2 MB)'
        db      0

STRUN   db      '** Undefined Function **'
        db      0

;
; Define format of WINDOW structure
;
Window  STRUC
   leftrow   dw     ?
   leftcol   dw     ?
   rightrow  dw     ?
   rightcol  dw     ?
   xwidth    dw     ?
   height    dw     ?
   startmem  dw     ?
Window  ENDS

;
; Allocate and initialize WINDOW parameters
;
W1        Window     <0, 42, 13, 79, 0, 0, 0>

Winseg    dw     0000                ; store segment of allocated memory
                                     ;  for saving screen data
Scrseg    dw     0000                ; base segment for screen memory
Pageseg   dw     0000                ; segment after page offset factored in
BytesPL   dw     80*2                ; number of bytes per line (char + attr)
Ret_Flg   db     00                  ; 'stop on return' flag
SegEnv    dw     0000                ; segment of environment
SegPSP    dw     0000                ; segment of PSP at load time
SegEND    dw     0000                ; program segment size
Step_Flg  db     00                  ; step flag
                                     ;  stop and display if bit 0 = 1
                                     ;  terminate STEP13 if bit 1 = 1
;
Skip_Typ  db     00                  ; type of function call to skip temporarily
Skip_Flg  db     00                  ; set if function should be skipped

Save13    dw     ?                   ; save original Int13 vector here, IP
          dw     ?                   ;  and SEG

Save28    dw     ?                   ; save original Int28 vector here, IP
          dw     ?                   ;  and SEG

Save1c    dw     ?                   ; save original Int1c vector here, IP
          dw     ?                   ;  and SEG

Ssave     LABEL  DWORD
SPsave    dw     ?                   ; save some of the user's registers here
SSsave    dw     ?                   ;  others will be on local stack

AXsave    dw     ?                   ; these will have been left on user's
DSsave    dw     ?                   ;  stack instead of local stack
CSsave    dw     ?
IPsave    dw     ?
FLsave    dw     ?
Last_FC   db     ?                   ; save 'AH' as last function code
;
INITmsg   db     CR
          db     LF
          db     '*** STEP13 Version 1.0 Installed ***'
          db     CR
          db     LF
          db     LF
          db     '   Press BOTH shift keys down to enable STEP13.'
          db     CR
          db     LF
          db     '   Press CONTROL and BOTH shift keys down to remove STEP13 '
          db     'from memory.'
          db     CR
          db     LF
          db     '$'

ERRmsg    db     CR
          db     LF
          db     'STEP13 already installed'
          db     '$'

FCStr     db     'Int 13H - Function XXH'
          db     0

DrvStr    db     'Drive:  XXH'
          db     0

HedStr    db     'Head:   XX'
          db     0

TrkStr    db     'Track: XXX'
          db     0

SecStr    db     'Sector: XX'
          db     0

CylStr    db     'Cyl:   XXX'
          db     0

BufStr    db     'Buffer Address = XXXX:XXXXH'
          db     0

RCStr     db     'Return Code = XXXXH'
          db     0

CFStr     db     'Carry Flag  = X'
          db     0

ZFStr     db     'Zero Flag   = X'
          db     0

HlpStr1   db     'kip Current Func'
          db     0

HlpStr2   db     'eturn Code'
          db     0

HlpStr3   db     ' - Non Stop'
          db     0

HlpStr4   db     ' - Move Window'
          db     0

HlpStr5   db     'Press Any Key'
          db     0

HlpStr6   db     'To Continue'
          db     0

Astr      db     32 dup (0)          ; string for converting ASCII characters

;
; Local Program Stack Area
;
          db     128 dup (?)
Pstack    EQU    $


IF TESTING
          db     128 dup (?)        ; Test Stack Area
Tstack    EQU    $

Buffer    db     512 dup (?)
ENDIF


PRSIZE    EQU     $

Code      ENDS

          END     Main

